package starter

import (
	"context"
	"fmt"
	"gitee.com/sourcandy/filter/ginv"
	"gitee.com/sourcandy/filter/logers"
	"gitee.com/sourcandy/filter/reader"
	"github.com/google/uuid"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/rs/zerolog/log"
	"github.com/soheilhy/cmux"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/protojson"
	"net"
	"net/http"
	"reflect"
	"time"
)

const GrpcConfig = "Grpcserver"

type Server struct {
	endpoint      string
	writer        *reader.Reader
	HTTPListener  net.Listener
	GRPCListener  net.Listener
	httpServer    *http.Server
	router        *http.ServeMux
	GRPClientConn *grpc.ClientConn
	ServerMux     *runtime.ServeMux
	tcpMux        cmux.CMux
}

type Handler func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error

// NewRpc returns a Server instance
func NewRpc() *Server {
	config, err := reader.NewSetting()
	s := &Server{
		writer: config,
	}
	s.endpoint = ":" + s.writer.GetStringMapString(GrpcConfig)["port"]
	listener, err := net.Listen("tcp", s.endpoint)
	if err != nil {
		panic(err)
	}
	s.tcpMux = cmux.New(listener)

	return s
}

// UseLog 日志记录
func (s *Server) UseLog() *Server {
	log.Logger = logers.NewLog(logers.WithWritePath(s.writer.GetStringMapString(GrpcConfig)["log_filepath"]),
		logers.WithLogExt(s.writer.GetStringMapString(GrpcConfig)["log_file_ext"])).Reader().Logger
	return s
}

// BindField 绑定数据
func (s *Server) BindField(f ...interface{}) *Server {
	for _, i2 := range f {
		t2 := reflect.TypeOf(i2).Elem()
		v2 := reflect.ValueOf(i2).Elem()
		for i := 0; i < v2.NumField(); i++ {
			InParamMapFunc(t2.Field(i).Type.Elem().String())(v2.Field(i), s.writer.Bind(t2.Field(i).Name))
		}
	}
	return s
}

func (s *Server) Start(server interface{}, handler Handler, srv interface{}) error {
	ctx := context.Background()
	ctx = context.WithValue(ctx, ginv.RequestId, uuid.New().String())
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	s.GRPCListener = s.tcpMux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldPrefixSendSettings("content-type", "application/grpc"))
	s.HTTPListener = s.tcpMux.Match(cmux.HTTP1Fast())

	go func() {
		v1 := reflect.ValueOf(server)
		grpcServer := grpc.NewServer()
		value := []reflect.Value{reflect.ValueOf(grpcServer), reflect.ValueOf(srv)}
		v1.Call(value)
		if err := grpcServer.Serve(s.GRPCListener); err != nil {
			panic(err)
		}
		//s.registerGRPC(ctx, s)
	}()

	go func() {
		if err := s.initGateway(ctx); err != nil {
			panic(err)
		}
		if err := handler(ctx, s.ServerMux, s.GRPClientConn); err != nil {
			panic(err)
		}
		s.startGateway()
	}()

	return s.tcpMux.Serve()
}

func (s *Server) Stop() {
	s.tcpMux.Close()
}

func (s *Server) initGateway(ctx context.Context) error {
	var err error
	s.router = http.NewServeMux()
	s.GRPClientConn, err = grpc.Dial(s.endpoint, []grpc.DialOption{
		grpc.WithTimeout(10 * time.Second),
		grpc.WithBlock(),
		grpc.WithInsecure(),
	}...)
	if err != nil {
		return fmt.Errorf("Fail to dial: %v", err)
	}

	s.ServerMux = runtime.NewServeMux(
		runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
			MarshalOptions: protojson.MarshalOptions{
				UseProtoNames:  true,
				UseEnumNumbers: true,
			},
		}),
		runtime.WithErrorHandler(runtime.DefaultHTTPErrorHandler),
	)
	return nil
}

func (s *Server) startGateway() {
	s.router.Handle("/", s.ServerMux)

	s.httpServer = &http.Server{
		Addr:         s.endpoint,
		Handler:      s.router,
		ReadTimeout:  time.Second,
		WriteTimeout: time.Second,
		IdleTimeout:  time.Second,
	}

	if err := s.httpServer.Serve(s.HTTPListener); err != nil {
		panic(err)
	}
}
